fix(sentinel): split Umbrella identities into flat host and user fields - #105
Merged
Conversation
Asked for a table of hostnames, a local model checked the Umbrella output, found no field named anything like "hostname", and went looking in Tenable and LimaCharlie instead. The hostname was in the rows it already had. `Identities_s` is a JSON array of two identities on 98% of dns rows (356,571 of 362,944 in 1h): element 0 the Anyconnect roaming client — a machine name, 739 distinct, none containing "@" — and element 1 the AD user, 724 distinct, all UPNs, ordered to match Identity_Types_s. PR #104 made that column searchable but left it shaped as a JSON array inside a single evidence value. That shape is the defect. The findings schema is flat by contract precisely so a small model never has to parse a value, and `list_sentinel_incidents` already learned this lesson the same way — live validation caught it returning `Owner` and the tactics inside `AdditionalData` as raw JSON strings, and both were parsed into readable evidence. The Umbrella surfaces never got the same treatment, so "who" was answerable and "which machine" was not. Rows now carry `identity_host` and `identity_user` as separate fields, with `Identity_Types_s` consumed as the classifier rather than echoed back as another array to parse. Classification is driven by the type array, not by position: the ordering is this connector's convention, not a guarantee. Where types are absent or do not line up, it falls back to shape ("@" means a user). The ~1% of rows carrying a third AD Groups element surface it as `identity_other` — calling a group a hostname would be the same class of error this split removes — and a truncated or non-JSON value is kept unparsed rather than dropped. Verified live: the seven blocked Tailscale hosts now report machine names (SBL8773, SB11199, SBL3878, …) alongside their users, in one call, with no raw array left in the output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XYTy7da8Z5ZHhkwcCpjojZ
|
Claude finished @ubercylon8's task in 1m 43s —— View job Security review — PR #105
Verdict: no security or correctness issues found. This is a well-scoped normalization fix, consistent with What I checked
No inline comments — didn't find anything rising to a reportable finding. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Asked for a table of hostnames, a local model checked the Umbrella output, found no field named anything like "hostname", and went looking in Tenable and LimaCharlie instead. The hostname was in the rows it already had.
What the column actually holds
Identities_sis a two-element JSON array on 98% of dns rows (356,571 of 362,944 in a 1h sample):@[0][1]matching the order of
Identity_Types_s(["Anyconnect Roaming Client","AD Users"]).Why this is our bug, not the model's
PR #104 made that column searchable but left it shaped as a JSON array inside a single flat evidence value. The findings schema is flat by contract precisely so a small model never has to parse a value to read it — and
list_sentinel_incidentsalready learned this the hard way: live validation caught it returningOwnerand the tactics insideAdditionalDataas raw JSON strings, and both were parsed into readable evidence. The Umbrella surfaces never got the same treatment. So "who" was answerable and "which machine" was not.The change
Rows now carry
identity_hostandidentity_useras separate fields, replacing the raw array in place so field grouping survives.Identity_Types_sis consumed as the classifier rather than echoed back as another array to parse.Classification is type-driven, not positional — the ordering is this connector's convention, not a guarantee. Fallbacks are deliberate:
@means a user)AD Groupselement → surfaced asidentity_other, because calling a group a hostname would be the same class of error this split exists to removeVerification
Live: the seven blocked Tailscale hosts now report machine names (
SBL8773,SB11199,SBL3878, …) alongside their users, in one call, with no raw array left in the output.1080 tests passing (+9), ruff and mypy clean, no
gen_docsdrift. Five mutations run; one initially survived — it hit the malformed-JSON branch my tests didn't cover, so a test was added and it now fails correctly.🤖 Generated with Claude Code
https://claude.ai/code/session_01XYTy7da8Z5ZHhkwcCpjojZ